# -----------------------------------------------------------------------------
#  Cmake File for using google test and cross paltform make
#  File Name      :  CMakeLists.txt
#  CMake Version  :  V3.17.2
#  Author         :  Shibo Jiang  
#  Instructions   :  Initial file.             2020/5/27                 V0.1
#                    Fix bug - multiple folders may cause err
#                                     2020/5/29    Shibo Jiang           V0.2 
#                    1. Add remote & local option for google test framework
#                    2. Change minimum cmake version to 3.11 from 3.15
#                                     2020/6/15    Shibo Jiang           V0.3
#                    1. Add mulitle folder support.
#                    2. Add gmock support
#                                       2020/6/19  Shibo Jiang           V0.4
#                    1. Add src folder spec/ignore function,
#                    2. Use set replace list to speed up cmake.
#                    3. Fix bugs which may find dupulicate src folder. 
#                                          2020/8/11    Shibo Jiang      V0.5
#                    1. Change ignore and specific folder ways, use regexp
#                       to filter names
#                                          2020/8/13    Shibo Jiang      V0.6
#                    1. Add lib source as input.
#                                          2020/9/23    Shibo Jiang      V0.7
#                    1. Support for RH850 with greenhill compiler
#                                          2020/11/19   shibo jiang      V0.8
#                    1. Fix bugs which may include unused folders
#                                          2020/11/19   shibo jiang      V0.8.1
#                    1. Support for ARM with greemhill compiler
#                                          2020/12/4    shibo jiang      V0.9
#                    1. Fix bugs which debug opiton can not work.
#                       Options set value can only be ON/OFF
#                    2. Support for ARM with gcc-eabi-none compiler
#                    3. Add src_files variables as append source files input.
#                                          2020/12/7    shibo jiang      V0.9.1
#                    1. Enable gtest in GCT project sample code.
#                                          2021/3/4     shibo jiang      v0.9.2
#                    Fix bugs which use remote git download gtest may fail.
#                                          2021/5/7     shibo jiang       0.9.3
# 
# -----------------------------------------------------------------------------

# user config -----------------------------------------------------------------
# whehter use debug mode, ON/OFF
option(CMAKE_DEBUG "Whether use debug type" ON)
# include(${CMAKE_CURRENT_SOURCE_DIR}/gcc_x86_toolchain.cmake)
# set(CMAKE_TOOLCHAIN_FILE "gcc_x86_toolchain.cmake")
set(target_name "gtest_sample")
# .h .c .cpp folder ,can auto recursive find source code
set(src_folder  "../src"
                "../test")

# append src (*.c *.h ...) setting.
set(src_files "")

# asm file list
list(APPEND CMAKE_ASM_SOURCE_FILE_EXTENSIONS S asm inc)
set(asm_files "")
SET_SOURCE_FILES_PROPERTIES(${asm_files} PROPERTIES LANGUAGE ASM)

# .a .o .obj .so .lib files folder , can not recursive find
set(lib_folder "")

# link file ,for embeded platform and cross platform complie
set(link_file "")

# optional - specific src folder name or use ignore name to fliter folder
set(src_spec "")
set(src_ignore "build" "html" "slprj")
add_definitions(-D _CMAKE_DEBUG)

# control unit test -----------------------------------------------------------
option(ENABLE_UNIT_TESTS "Enable unit tests" ON)
option(DOWNLOAD_FROM_GIT "Download google test by git from remote repo." ON)
# If DOWNLOAD_FROM_GIT = OFF ,gtest_folder should be 
# seted to gtest src folder.
set(gtest_folder "./googletest-release-1.10.0")

# system config ,do not change or change it carefully--------------------------
# set minimum cmake version
cmake_minimum_required(VERSION 3.10 FATAL_ERROR)
message(STATUS "Target name is set to ${target_name}")
message(STATUS "Generator is set to ${CMAKE_GENERATOR}")

# project name and language
project(${target_name}_prj LANGUAGES C CXX ASM)

# require C++11
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(CMAKE_C_STANDARD 99)
set(CMAKE_C_EXTENSIONS OFF)
set(CMAKE_C_STANDARD_REQUIRED ON)

set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)

# specific folder name and ignore name filter setting report
if(src_spec)
  # use specific folder name
  message(STATUS "src folder specific name = ${src_spec}")
  if(src_ignore)
    # use ignore folder name 
    message(STATUS "src folder ignore name = ${src_ignore}")
  endif()
elseif(src_ignore)
  # use ignore folder name 
  message(STATUS "src folder ignore name = ${src_ignore}")
else()
  # do not use sepcific and ignore
  message(STATUS "do not use src_spec and src_ignore, only use src_folder")
endif()

message(STATUS "src subfolder name:")
# funciton declation for recursively find file
MACRO(SUBDIRLIST result curdir)
  SET(dirlist "")
  SET(spec_dir_index "")
  SET(ignore_dir_index "")
  LIST(APPEND dirlist ${curdir})
  FILE(GLOB_RECURSE children LIST_DIRECTORIES true ${curdir}/*)
  FOREACH(child ${children})
    IF(IS_DIRECTORY ${child})
      # use specific folder name to judge
      # STRING(REGEX REPLACE ".*/" "" sub_folder ${child})
      SET(spec_dir_index -1)
      SET(ignore_dir_index -1)
      IF(src_spec)
        # LIST(FIND src_spec ${sub_folder} spec_dir_index)
        # find specific name
        FOREACH(sub_spec ${src_spec})
          # use regexp patten to find specific folder name
          SET(reg_patten ".*/${sub_spec}/|/${sub_spec}$")
          STRING(REGEX MATCH ${reg_patten} spec_flg ${child})
          IF(spec_flg)
            SET(spec_dir_index 0)
            break()
          ENDIF()
        ENDFOREACH()
        # use ignore name to filter
        IF(src_ignore)
          FOREACH(sub_ignore ${src_ignore})
            # use regexp patten to find specific folder name
            SET(reg_patten ".*/${sub_ignore}/|/${sub_ignore}$")
            STRING(REGEX MATCH ${reg_patten} spec_flg ${child})
            IF(spec_flg)
              SET(ignore_dir_index 0)
              break()
            ENDIF()
          ENDFOREACH()
        ENDIF()
        # message("spec_dir_index = ${spec_dir_index}" )
        IF ((spec_dir_index GREATER -1) AND (ignore_dir_index EQUAL -1))
          # only have file ,then add folder to list 
          FILE(GLOB tp_files ${child}/*.*)
          IF(tp_files)
            LIST(APPEND dirlist ${child})
          ELSE()
            # message("******** 1 This folder has no files, do not add to list!*** ${child}")
          ENDIF()
          # message("child_folder = ${child}")
          # message("sub_folder = ${sub_folder}")
        ENDIF()
      # use ignore folder name to judge
      ELSEIF(src_ignore)
        # LIST(FIND src_ignore ${sub_folder} ignore_dir_index)
        # message("ignore_dir_index = ${ignore_dir_index}")
        FOREACH(sub_ignore ${src_ignore})
          # use regexp patten to find specific folder name
          SET(reg_patten ".*/${sub_ignore}/|/${sub_ignore}$")
          STRING(REGEX MATCH ${reg_patten} spec_flg ${child})
          IF(spec_flg)
            SET(ignore_dir_index 0)
            break()
          ENDIF()
        ENDFOREACH()
        IF (ignore_dir_index EQUAL -1)
          # only have file ,then add folder to list 
          SET(tp_files "")
          FILE(GLOB tp_files ${child}/*.*)
          IF(tp_files)
            LIST(APPEND dirlist ${child})
            # message("******** 2 This folder added to list!*** ${child}")
            # message("${tp_files}")
          ELSE()
            # message("******** 2 This folder has no files, do not add to list!*** ${child}")
            # message("${tp_files}")
          ENDIF()
          # message("child_folder = ${child}")
          # message("sub_folder = ${sub_folder}")
        ENDIF()
      ELSE()
          # only have file ,then add folder to list 
          FILE(GLOB tp_files ${child}/*.*)
          IF(tp_files)
            LIST(APPEND dirlist ${child})
          ELSE()
            # message("******** 3 This folder has no files, do not add to list!*** ${child}")
          ENDIF()
        # message("child_folder = ${child}")
        # message("sub_folder = ${sub_folder}")
      ENDIF()
    ENDIF()
  ENDFOREACH()
  SET(${result} ${dirlist})
ENDMACRO()

# debug code ------------------------------------------------------------------
# set(temp_ignore_flg "")
# list(FIND src_ignore "test" temp_ignore_flg)
# message("temp_ignore_flg = ${temp_ignore_flg}")
# set(src_spec_len "")
# list(LENGTH src_spec src_spec_len)
# message("src_spec_len = ${src_spec_len}")
# if(src_spec)
#   message("src_spec ok")
# else()
#   message("src_spec not ok")
# endif()
# if(src_ignore)
#   message("src_ignore ok")
# else()
#   message("src_ignore not ok")
# endif()
# ----------------------------------------------------------------------------- 

# project file and folder
set(SUBDIRS "")
foreach(sub_src_folder ${src_folder})
  SUBDIRLIST(TEMP_SUBDIRS ${sub_src_folder})
  list(APPEND SUBDIRS ${SUBDIRS} ${TEMP_SUBDIRS})
  # include_directories(${sub_dir})
  # aux_source_directory(${sub_dir} temp_src_list)
  # message(${temp_src_list})
endforeach()
# remove dupulicate file and print src file
list(REMOVE_DUPLICATES SUBDIRS)
# add src folder and include folder
set(src_list "")
# set(file_no "")
message("\n#### source file list: ####")
foreach(sub_dir ${SUBDIRS})
  set(temp_src_list "")
  include_directories(${sub_dir})
  aux_source_directory(${sub_dir} temp_src_list)
  # list(APPEND src_list ${src_list} ${temp_src_list})
  foreach(tp_file ${temp_src_list})
    # list(APPEND src_list ${src_list} ${tp_file})
    # message(${tp_file})
    set(src_list ${src_list} ${tp_file})
  endforeach()
endforeach()
# combine asm files
if(asm_files)
  message(STATUS "Project contain asm files: ")
  set(src_list ${src_list} ${asm_files})
  foreach(tp_file ${asm_files})
    # message(${tp_file})
  endforeach()
endif()
# combine append src files
if(src_files)
  message(STATUS "Project contain append src files: ")
  set(src_list ${src_list} ${src_files})
  foreach(tp_file ${src_files})
    # message(${tp_file})
  endforeach()
endif()
# get source file number
list(LENGTH src_list files_no)
message(STATUS "source files no. = ${files_no}")
# message(${src_list})
# remove dupulicate file and print src file
# list(REMOVE_DUPLICATES src_list)
# message(STATUS "source file list: ")
# foreach(one_file ${src_list})
#   message("${one_file}")
# endforeach()

if(ENABLE_UNIT_TESTS)
  # src library - static
  # add_library(${target_name}_lib
  #             STATIC
  #             ${src_list})
else()
  # set .out file
  set(EXECUTABLE ${target_name})
  add_executable(${EXECUTABLE} ${src_list})
  # target_compile_definitions(${EXECUTABLE} PRIVATE
  #                             ${C_OPTIONS})
  # target_compile_options(${EXECUTABLE} PRIVATE
  #                         ${C_OPTIONS})
  if(link_file)
    target_link_options(${EXECUTABLE} PRIVATE
                -T ${link_file})
  endif()
endif()

# debug
# message(STATUS "C_OPTIONS:  ${C_OPTIONS}")
# message(STATUS "LD_OPTIONS:  ${LD_OPTIONS}")
# message(STATUS "${CMAKE_C_LINK_EXECUTABLE}")
# get lib files
if (lib_folder)
  message("\n#### Extern Lib Files ####")
  foreach(lib_dir ${lib_folder})
    # add lib folder path 
    link_directories(${lib_dir})
    # add lib files to 
    file(GLOB lib_a_files "${lib_dir}/*.a")
    file(GLOB lib_o_files "${lib_dir}/*.o")
    file(GLOB lib_obj_files "${lib_dir}/*.obj")
    file(GLOB lib_so_files "${lib_dir}/*.so")
    file(GLOB lib_lib_files "${lib_dir}/*.lib")
    list(APPEND lib_files ${lib_a_files} 
                          ${lib_obj_files}
                          ${lib_o_files}
                          ${lib_so_files}
                          ${lib_lib_files})
    # message(STATUS "Lib folder: ${lib_dir}")
  endforeach()
  # report files
  # foreach(tp_file ${lib_files})
    # message(${tp_file})
  # endforeach()
  list(LENGTH lib_files lib_files_no)
  message(STATUS "lib files no. = ${lib_files_no}")
  # link lib to excute
  target_link_libraries(${EXECUTABLE}
                        ${lib_files})
else()
  # target_link_libraries(${target_name}_lib ${target_name})
endif()

# main code and lib link
# if (main_file)
#   message(STATUS "Use main function to creat run object : \n ${main_file}")
#   if(link_file)
#     add_executable(${target_name}_run ${main_file} ${link_file})
#   else()
#     add_executable(${target_name}_run ${main_file})
#   endif()
#   target_link_libraries(${target_name}_run ${EXECUTABLE})
# else()
#   message(STATUS "No main function.")
# endif()

# Post build ------------------------------------------------------------------
# Create elf s19 file
# add_custom_command(TARGET ${EXECUTABLE}
#         POST_BUILD
#         COMMAND ${CMAKE_GSREC_TOOL} -noS5 ${EXECUTABLE} -o ${target_name}.s19)
# Print executable size
# add_custom_command(TARGET ${EXECUTABLE}
#         POST_BUILD
#         COMMAND ${CMAKE_GSIZE_TOOL} ${EXECUTABLE}
#         COMMAND echo [Work done! Generated ${target_name}])

# make it possible to disable unit tests
message("")
message(STATUS "Enable testing: ${ENABLE_UNIT_TESTS}")

# use google test to complie and test -----------------------------------------
if(ENABLE_UNIT_TESTS)
  message(STATUS "Preparing google test framework ...")
  message("DOWNLOAD_FROM_GIT : ${DOWNLOAD_FROM_GIT}")
  # Use remote google test repo.
  if(DOWNLOAD_FROM_GIT)
    message("Download from remote git repository : ")
    message("https://gitee.com/mirrors/googletest")
    # the following code to fetch googletest
    # is inspired by and adapted after:
    #   - https://cmake.org/cmake/help/v3.11/module/FetchContent.html
    include(FetchContent)
    # Use the network to fetch Google Test sources
    FetchContent_Declare(
      googletest
      GIT_REPOSITORY git@gitee.com:mirrors/googletest.git
      GIT_TAG        release-1.10.0
    )

    FetchContent_GetProperties(googletest)

    if(NOT googletest_POPULATED)
      FetchContent_Populate(googletest)

      # Prevent GoogleTest from overriding our compiler/linker options
      # when building with Visual Studio
      set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
      # Prevent GoogleTest from using PThreads
      set(gtest_disable_pthreads ON CACHE BOOL "" FORCE)

      # adds the targers: gtest, gtest_main, gmock, gmock_main
      message("googletest_SOURCE_DIR : ${googletest_SOURCE_DIR}")
      message("googletest_BINARY_DIR : ${googletest_BINARY_DIR}")
      add_subdirectory(
        ${googletest_SOURCE_DIR}
        ${googletest_BINARY_DIR}
        )
    endif()
  # Use local googlt test src folder. -----------------------------------------
  else()
    message("Use local gtest project : ")
    message(gtest_folder : ${gtest_folder})
    include_directories(${gtest_folder}/googletest/include)
    include_directories(${gtest_folder}/googlemock/include)
    add_subdirectory(${gtest_folder})
  endif()
  # Silence std::tr1 warning on MSVC
  if(MSVC)
    foreach(_tgt gtest gtest_main gmock gmock_main)
      target_compile_definitions(${_tgt}
        PRIVATE
          "_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING"
        )
    endforeach()
  endif()  
  message("google test framework download and prepare OK !")
  # Complie google test to gtest_main static lib ------------------------------
  add_executable(${target_name}_gtest "")

  target_sources(${target_name}_gtest
    PRIVATE
      ${src_list}
    )

  target_link_libraries(${target_name}_gtest
    PRIVATE
      # ${target_name}_lib
      gtest_main
      gmock_main
    )

  enable_testing()

  add_test(
    NAME google_test
    COMMAND $<TARGET_FILE:gtest_main>
    )
endif()
# end of file -----------------------------------------------------------------